//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

/**
 * Support for functions that uses mmap.
 */

#include "pmfunction.h"
#include "pminternal.h"

static size_t pagesize = 0;

#define PAGE_ALIGN(addr) \
    reinterpret_cast<void *>((reinterpret_cast<long>(addr) + pagesize - 1) & ~(pagesize - 1))

/**
 * 默认的morespace：修改内存池管理结构中关于内存空间的信息，拓展空间
 */
void *__pmalloc_map_morespace(struct mdesc *mdp, const ptrdiff_t size)
{
    void *result = nullptr;
    size_t mapbytes;
    void *moveto;
    void *mapto;
    const char buf = 0;

    if (0 == pagesize) {
        pagesize = getpagesize();  //获得内存分页大小，存储在静态变量
    }
    if (0 == size) {
        result = mdp->watermarkpos;
    }
    else if (size < 0) {
        if ((mdp->watermarkpos + size) >= mdp->mempoolbase) {
            result = static_cast<void *>(mdp->watermarkpos);
            mdp->watermarkpos += size;
        }
    }
    else {
        if ((mdp->watermarkpos + size) > mdp->limitpos) {
            // todo :
            // 这里有问题，该内存池很明显没有考虑对baseaddr自主决定的可能。
            // 需要把这里的初始化方式重写，有效baseaddr的情况下和pmalloc_attach中写法矛盾
            // Initial memory pool

            if ((nullptr != mdp->mempoolbase) && (nullptr == mdp->limitpos)) {
                //内存池初始地址为用户自定义：目前仅考虑
                //匿名映射下的映射地址自定义
                assert(nullptr == mdp->watermarkpos);

                if (mdp->fixedSize) {
                    mdp->watermarkpos = mdp->mempoolbase;
                    mdp->watermarkpos += size;
                    mdp->limitpos = static_cast<char *>(PAGE_ALIGN(mdp->watermarkpos));

                    return mdp->mempoolbase;
                }

                mapbytes = reinterpret_cast<size_t>(PAGE_ALIGN(size));
                mapto = mmap(mdp->mempoolbase, mapbytes, PROT_READ | PROT_WRITE,
                             MAP_SHARED | MAP_ANON, mdp->mappingfd, 0);
                if (mapto != MAP_FAILED) {
                    result = mapto;
                    mdp->mempoolbase = static_cast<char *>(result);
                    mdp->watermarkpos = static_cast<char *>(result);
                    mdp->watermarkpos += size;
                    mdp->limitpos = static_cast<char *>(PAGE_ALIGN(mdp->watermarkpos));
                }
                else {
    /**
     * ref: https://stackoverflow.com/questions/24562691/why-does-mmap-use-map-failed-instead-of-null
     *
     * There are some rare situations where mmap() will actually create a
     * mapping at address 0x0. These days, it typically requires root
     * privileges (or for the mmap_min_addr sysctl to be set to zero on
     * Linux systems) but it is possible. If such a mapping is created, it
     * becomes possible to write to this address.
     * MAP_FAILED, on the other hand, is never a valid return value from
     * mmap(), so it's usable as a sentinel.
     */
                    //fprintf(stderr, "Line 52 Mapping ERROR:(%d) %s \n", errno,
                    //        strerror(errno));
                    return nullptr;
                }
            }
            else if (nullptr == mdp->limitpos) {
                //内存池初始地址为nullptr
                assert(nullptr == mdp->watermarkpos);
                moveto = PAGE_ALIGN(size);
                mapbytes = reinterpret_cast<size_t>(moveto);

                if (! (mdp->flags & (PMALLOC_DEVZERO | PMALLOC_ANON))) {
                    lseek(mdp->mappingfd, mapbytes - 1, SEEK_SET);
                    if (write(mdp->mappingfd, &buf, 1) != 1) {
                        return nullptr;
                    }
                }

                if (mdp->flags & PMALLOC_ANON) {
                    mapto = mmap(nullptr, mapbytes, PROT_READ | PROT_WRITE,
                                 MAP_SHARED | MAP_ANON, mdp->mappingfd, 0);
                }
                else {
                    mapto = mmap(nullptr, mapbytes, PROT_READ | PROT_WRITE,
                                 MAP_SHARED, mdp->mappingfd, 0);
                }

                // about MAP_FAILED, see above.
                if (mapto != MAP_FAILED) {                  // initial
                    result = mapto;
                    mdp->mempoolbase = static_cast<char *>(result);
                    mdp->watermarkpos = static_cast<char *>(result);
                    mdp->watermarkpos += size;
                    mdp->limitpos = static_cast<char *>(PAGE_ALIGN(mdp->watermarkpos));
                }
                else {
                    // fprintf(stderr, "Line 83 Mapping ERROR:(%d) %s \n", errno,
                    //        strerror(errno));
                    return nullptr;
                }
            }
        }
        else {
            // 返回的result为增加空间前的水位
            result = static_cast<void *>(mdp->watermarkpos);
            mdp->watermarkpos += size;
        }
    }
    return (result);
}

/**
 * 对之前被用过初始化内存池的文件，进行读取管理结构而复用
 * 仅在pmfunction.cpp : reuse_mempool中被调用
 */
void *__pmalloc_remap_mempool(struct mdesc *mdp)
{
    void *base;

    base = mmap(mdp->mempoolbase, static_cast<size_t>(mdp->limitpos - mdp->mempoolbase),
                PROT_READ | PROT_WRITE, MAP_SHARED, mdp->mappingfd, 0);
    if (base == MAP_FAILED) {
        // about MAP_FAILED, see above.

        //fprintf(stderr, "Mapping ERROR:(%d) %s \n", errno, strerror(errno));
        return nullptr;

    }
    return base;
}
